1import { useEffectEvent, useState } from "react";
2import { Platform, Pressable, StyleSheet, View, type AccessibilityActionEvent } from "react-native";
3import { Gesture, GestureDetector } from "react-native-gesture-handler";
4import { runOnJS } from "react-native-reanimated";
5import { cn } from "../../lib/cn";
6
7const ACCESSIBILITY_RESIZE_STEP = 24;
8
9interface WorkspacePaneDividerProps {
10 readonly accessibilityLabel: string;
11 readonly currentWidth: number;
12 readonly resizeDirection: 1 | -1;
13 readonly onResizeStart?: () => void;
14 readonly onResizeBy: (delta: number) => void;
15 readonly onResizeEnd?: () => void;
16}
17
18export function WorkspacePaneDivider(props: WorkspacePaneDividerProps) {
19 const [dragging, setDragging] = useState(false);
20 const handleResizeStart = useEffectEvent(() => {
21 setDragging(true);
22 props.onResizeStart?.();
23 });
24 const handleResize = useEffectEvent((translationX: number) => {
25 props.onResizeBy(translationX * props.resizeDirection);
26 });
27 const handleResizeEnd = useEffectEvent(() => {
28 setDragging(false);
29 props.onResizeEnd?.();
30 });
31 const [resizeGesture] = useState(() =>
32 Gesture.Pan()
33 .activeOffsetX([-4, 4])
34 .failOffsetY([-24, 24])
35 .onStart(() => {
36 runOnJS(handleResizeStart)();
37 })
38 .onUpdate((event) => {
39 runOnJS(handleResize)(event.translationX);
40 })
41 .onFinalize(() => {
42 runOnJS(handleResizeEnd)();
43 }),
44 );
45
46 const handleAccessibilityAction = (event: AccessibilityActionEvent) => {
47 props.onResizeStart?.();
48 if (event.nativeEvent.actionName === "increment") {
49 props.onResizeBy(ACCESSIBILITY_RESIZE_STEP);
50 } else if (event.nativeEvent.actionName === "decrement") {
51 props.onResizeBy(-ACCESSIBILITY_RESIZE_STEP);
52 }
53 props.onResizeEnd?.();
54 };
55
56 return (
57 <GestureDetector gesture={resizeGesture}>
58 <Pressable
59 className="relative z-[100] -mx-[22px] w-[44px] self-stretch justify-center"
60 accessibilityActions={[
61 { name: "increment", label: "Make pane wider" },
62 { name: "decrement", label: "Make pane narrower" },
63 ]}
64 accessibilityLabel={props.accessibilityLabel}
65 accessibilityRole="adjustable"
66 accessibilityValue={{
67 now: Math.round(props.currentWidth),
68 text: `${Math.round(props.currentWidth)} points wide`,
69 }}
70 onAccessibilityAction={handleAccessibilityAction}
71 >
72 <View
73 className={cn(
74 "h-full self-center bg-border opacity-70",
75 dragging ? "w-0.5 bg-primary opacity-100" : "w-px",
76 Platform.OS === "android" && !dragging && "opacity-0",
77 )}
78 style={[styles.line, dragging && styles.activeLine]}
79 />
80 </Pressable>
81 </GestureDetector>
82 );
83}
84
85const styles = StyleSheet.create({
86 line: {
87 alignSelf: "center",
88 height: "100%",
89 width: StyleSheet.hairlineWidth,
90 },
91 activeLine: {
92 width: 2,
93 },
94});