Skip to content
Source
Sign inSign up

workspace-inspector-pane.tsx

126 lines4.3 KB

1import { useEffect, useRef, useState } from "react";
2import Animated, {
3 cubicBezier,
4 runOnJS,
5 useAnimatedStyle,
6 useReducedMotion,
7 useSharedValue,
8 withTiming,
9 type SharedValue,
10} from "react-native-reanimated";
11​
12import { constrainAuxiliaryPaneWidth, type WorkspacePaneLayout } from "../../lib/layout";
13import { WORKSPACE_PANE_TIMING } from "./workspace-pane-animation";
14import { WorkspacePaneDivider } from "./workspace-pane-divider";
15import { InspectorPanes } from "./inspector-host";
16import type { InspectorContent } from "./inspector-panes";
17​
18// CSS form of WORKSPACE_PANE_TIMING's Easing.inOut(Easing.cubic).
19const PANE_EASING = cubicBezier(0.65, 0, 0.35, 1);
20​
21/** Pass layout props to avoid an import cycle with AdaptiveWorkspaceLayout. */
22export function WorkspaceInspectorPane(props: {
23 readonly renderedInspectorWidth: SharedValue<number>;
24 readonly active?: boolean;
25 readonly onClose: () => void;
26 readonly onClosed?: () => void;
27 readonly panes: WorkspacePaneLayout;
28 readonly content: InspectorContent | undefined;
29 readonly setAuxiliaryPaneWidth: (width: number) => void;
30}) {
31 const { panes, setAuxiliaryPaneWidth } = props;
32 const inspectorWidth = panes.auxiliaryPaneWidth;
33 const inspectorSupported = props.content !== undefined && inspectorWidth !== null;
34 const inspectorVisible =
35 inspectorSupported && panes.auxiliaryPaneVisible && (props.active ?? true);
36 const resizeStartWidth = useRef(0);
37 const [resizing, setResizing] = useState(false);
38​
39 const inspectorProgress = useSharedValue(inspectorVisible ? 1 : 0);
40 const { renderedInspectorWidth } = props;
41 const reduceMotion = useReducedMotion();
42​
43 const onClosed = props.onClosed;
44 // oxlint-disable-next-line t3code/no-unreasoned-state-sync -- animates the Reanimated inspector progress and width shared values when visibility or width changes
45 useEffect(() => {
46 inspectorProgress.set(
47 withTiming(inspectorVisible ? 1 : 0, WORKSPACE_PANE_TIMING, (finished) => {
48 if (finished === true && !inspectorVisible && onClosed !== undefined) {
49 runOnJS(onClosed)();
50 }
51 }),
52 );
53 const targetWidth = inspectorVisible ? (inspectorWidth ?? 0) : 0;
54 renderedInspectorWidth.set(
55 resizing ? targetWidth : withTiming(targetWidth, WORKSPACE_PANE_TIMING),
56 );
57 }, [
58 inspectorProgress,
59 inspectorVisible,
60 inspectorWidth,
61 onClosed,
62 renderedInspectorWidth,
63 resizing,
64 ]);
65​
66 const inspectorStyle = useAnimatedStyle(
67 () => ({
68 opacity: inspectorProgress.value,
69 transform: [{ translateX: (1 - inspectorProgress.value) * 24 }],
70 width: renderedInspectorWidth.value,
71 }),
72 [],
73 );
74 // A hidden or resizing pane takes its width at once; a visible one eases with the column.
75 const inspectorContentStyle = {
76 width: inspectorWidth ?? 0,
77 transitionProperty: "width",
78 transitionDuration:
79 !inspectorVisible || resizing || reduceMotion ? 0 : WORKSPACE_PANE_TIMING.duration,
80 transitionTimingFunction: PANE_EASING,
81 } as const;
82 const beginResize = () => {
83 resizeStartWidth.current = inspectorWidth ?? 0;
84 setResizing(true);
85 };
86 const resizeBy = (delta: number) => {
87 setAuxiliaryPaneWidth(
88 constrainAuxiliaryPaneWidth({
89 preferredWidth: resizeStartWidth.current + delta,
90 availableWidth: panes.contentPaneWidth,
91 }),
92 );
93 };
94 const endResize = () => {
95 setResizing(false);
96 };
97​
98 return (
99 <>
100 {inspectorVisible ? (
101 <WorkspacePaneDivider
102 accessibilityLabel="Resize detail pane"
103 currentWidth={inspectorWidth ?? 0}
104 resizeDirection={-1}
105 onResizeStart={beginResize}
106 onResizeBy={resizeBy}
107 onResizeEnd={endResize}
108 />
109 ) : null}
110 {inspectorSupported ? (
111 <Animated.View
112 className="shrink-0 overflow-hidden"
113 accessibilityElementsHidden={!inspectorVisible}
114 collapsable={false}
115 importantForAccessibility={inspectorVisible ? "auto" : "no-hide-descendants"}
116 pointerEvents={inspectorVisible ? "auto" : "none"}
117 style={inspectorStyle}
118 >
119 <Animated.View className="flex-1" style={inspectorContentStyle}>
120 <InspectorPanes content={props.content} onClose={props.onClose} />
121 </Animated.View>
122 </Animated.View>
123 ) : null}
124 </>
125 );