feat(app): add shared desktop web overlay scroll handles

This commit is contained in:
Mohamed Boudra
2026-02-18 15:10:20 +07:00
parent b1955cf74d
commit 5079ca386b
11 changed files with 639 additions and 47 deletions
@@ -5,6 +5,9 @@ import {
FlatList,
Image as RNImage,
ListRenderItemInfo,
type LayoutChangeEvent,
type NativeScrollEvent,
type NativeSyntheticEvent,
Pressable,
ScrollView as RNScrollView,
Text,
@@ -63,6 +66,10 @@ import {
type SortOption,
} from "@/stores/panel-store";
import { formatTimeAgo } from "@/utils/time";
import {
WebDesktopScrollbarOverlay,
useWebDesktopScrollbarMetrics,
} from "@/components/web-desktop-scrollbar";
const SORT_OPTIONS: { value: SortOption; label: string }[] = [
{ value: "name", label: "Name" },
@@ -86,6 +93,7 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) {
const { theme } = useUnistyles();
const isMobile =
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
const showDesktopWebScrollbar = Platform.OS === "web" && !isMobile;
const { connectionStates } = useDaemonConnections();
const daemonProfile = connectionStates.get(serverId)?.daemon;
@@ -136,6 +144,8 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) {
const [expandedPaths, setExpandedPaths] = useState<Set<string>>(() => new Set(["."]));
const [containerWidth, setContainerWidth] = useState(0);
const wasInlinePreviewVisibleRef = useRef(false);
const treeListRef = useRef<FlatList<TreeRow>>(null);
const treeScrollbarMetrics = useWebDesktopScrollbarMetrics();
// Bottom sheet for file preview (mobile)
const previewSheetRef = useRef<BottomSheetModal>(null);
@@ -592,6 +602,24 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) {
);
}
const handleTreeListScroll = useCallback(
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
if (showDesktopWebScrollbar) {
treeScrollbarMetrics.onScroll(event);
}
},
[showDesktopWebScrollbar, treeScrollbarMetrics]
);
const handleTreeListLayout = useCallback(
(event: LayoutChangeEvent) => {
if (showDesktopWebScrollbar) {
treeScrollbarMetrics.onLayout(event);
}
},
[showDesktopWebScrollbar, treeScrollbarMetrics]
);
return (
<View
style={styles.container}
@@ -704,15 +732,41 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) {
</View>
</View>
<FlatList
ref={treeListRef}
style={styles.treeList}
data={treeRows}
renderItem={renderTreeRow}
keyExtractor={(row) => row.entry.path}
testID="file-explorer-tree-scroll"
contentContainerStyle={styles.entriesContent}
onLayout={
showDesktopWebScrollbar ? handleTreeListLayout : undefined
}
onScroll={
showDesktopWebScrollbar ? handleTreeListScroll : undefined
}
onContentSizeChange={
showDesktopWebScrollbar
? treeScrollbarMetrics.onContentSizeChange
: undefined
}
scrollEventThrottle={showDesktopWebScrollbar ? 16 : undefined}
showsVerticalScrollIndicator={!showDesktopWebScrollbar}
initialNumToRender={24}
maxToRenderPerBatch={40}
windowSize={12}
/>
<WebDesktopScrollbarOverlay
enabled={showDesktopWebScrollbar}
metrics={treeScrollbarMetrics}
onScrollToOffset={(nextOffset) => {
treeListRef.current?.scrollToOffset({
offset: nextOffset,
animated: false,
});
treeScrollbarMetrics.setOffset(nextOffset);
}}
/>
</Animated.View>
) : (
<View style={[styles.treePane, styles.treePaneFill]}>
@@ -741,15 +795,41 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) {
</View>
</View>
<FlatList
ref={treeListRef}
style={styles.treeList}
data={treeRows}
renderItem={renderTreeRow}
keyExtractor={(row) => row.entry.path}
testID="file-explorer-tree-scroll"
contentContainerStyle={styles.entriesContent}
onLayout={
showDesktopWebScrollbar ? handleTreeListLayout : undefined
}
onScroll={
showDesktopWebScrollbar ? handleTreeListScroll : undefined
}
onContentSizeChange={
showDesktopWebScrollbar
? treeScrollbarMetrics.onContentSizeChange
: undefined
}
scrollEventThrottle={showDesktopWebScrollbar ? 16 : undefined}
showsVerticalScrollIndicator={!showDesktopWebScrollbar}
initialNumToRender={24}
maxToRenderPerBatch={40}
windowSize={12}
/>
<WebDesktopScrollbarOverlay
enabled={showDesktopWebScrollbar}
metrics={treeScrollbarMetrics}
onScrollToOffset={(nextOffset) => {
treeListRef.current?.scrollToOffset({
offset: nextOffset,
animated: false,
});
treeScrollbarMetrics.setOffset(nextOffset);
}}
/>
</View>
)}
</View>