import { useCallback, useEffect, useRef, useState } from "react"; import { Pressable, View } from "react-native"; import { GestureDetector, type GestureType } from "react-native-gesture-handler"; import { StyleSheet } from "react-native-unistyles"; import { resolveSidebarResizeHandleGeometry, type SidebarResizeEdge, } from "@/components/sidebar-resize-handle-layout"; import { isWeb } from "@/constants/platform"; import { useHasFinePointer } from "@/hooks/use-fine-pointer"; interface SidebarResizeHandleProps { edge: SidebarResizeEdge; gesture: GestureType; pressed: boolean; testID: string; } const HIGHLIGHT_DELAY_MS = 100; const webResizeCursorStyle = isWeb ? ({ cursor: "col-resize", } as object) : null; function edgeOffsetStyle(edge: SidebarResizeEdge, edgeOffset: number) { return edge === "left" ? { left: edgeOffset } : { right: edgeOffset }; } export function SidebarResizeHandle({ edge, gesture, pressed, testID }: SidebarResizeHandleProps) { const finePointer = useHasFinePointer(); if (finePointer) { return ; } return ; } function PointerResizeHandle({ edge, gesture, testID }: SidebarResizeHandleProps) { const [highlighted, setHighlighted] = useState(false); const highlightTimerRef = useRef | null>(null); const geometry = resolveSidebarResizeHandleGeometry(true); const hitAreaStyle = [ styles.hitArea, { width: geometry.width }, edgeOffsetStyle(edge, geometry.edgeOffset), webResizeCursorStyle, ]; const cancelHighlightTimer = useCallback(() => { if (highlightTimerRef.current === null) return; clearTimeout(highlightTimerRef.current); highlightTimerRef.current = null; }, []); const handleHoverIn = useCallback(() => { cancelHighlightTimer(); highlightTimerRef.current = setTimeout(() => { highlightTimerRef.current = null; setHighlighted(true); }, HIGHLIGHT_DELAY_MS); }, [cancelHighlightTimer]); const handleHoverOut = useCallback(() => { cancelHighlightTimer(); setHighlighted(false); }, [cancelHighlightTimer]); useEffect(() => cancelHighlightTimer, [cancelHighlightTimer]); return ( {highlighted ? ( ) : null} ); } function TouchResizeHandle({ edge, gesture, pressed, testID }: SidebarResizeHandleProps) { const geometry = resolveSidebarResizeHandleGeometry(false); // `box-none` keeps the full-height column out of hit-testing so only the // grab target steals taps from the rows behind it. const layerStyle = [ styles.touchLayer, { width: geometry.width }, edgeOffsetStyle(edge, geometry.edgeOffset), ]; const targetStyle = [ styles.touchTarget, { width: geometry.width, height: geometry.height ?? undefined }, ]; const gripStyle = [ styles.grip, edge === "left" ? styles.leftEdgeGrip : styles.rightEdgeGrip, pressed ? styles.visibleGrip : styles.hiddenGrip, ]; return ( ); } const styles = StyleSheet.create((theme) => ({ hitArea: { position: "absolute", top: 0, bottom: 0, zIndex: 10, }, highlight: { position: "absolute", top: 0, bottom: 0, left: 5, width: 1, backgroundColor: theme.colors.foreground, opacity: 0.25, }, touchLayer: { position: "absolute", top: 0, bottom: 0, zIndex: 10, alignItems: "center", justifyContent: "center", }, touchTarget: { alignItems: "center", justifyContent: "center", }, grip: { width: 4, height: 36, borderRadius: 2, backgroundColor: theme.colors.foreground, }, hiddenGrip: { opacity: 0, }, visibleGrip: { opacity: 0.3, }, leftEdgeGrip: { alignSelf: "flex-start", marginLeft: theme.spacing[0.5], }, rightEdgeGrip: { alignSelf: "flex-end", marginRight: theme.spacing[0.5], }, }));