refactor(app): unified panel state machine for mobile sidebars
Replace separate sidebar stores with a unified panel store that uses a discriminated union state machine on mobile. This makes it impossible for both the agent list and file explorer sidebars to be open at the same time on mobile. Mobile state machine (mobileView): - 'agent': Main agent view (no overlay panel) - 'agent-list': Agent list sidebar (left overlay) - 'file-explorer': File explorer sidebar (right overlay) Desktop retains independent boolean toggles since sidebars sit alongside content rather than overlaying it. Key changes: - Created stores/panel-store.ts with unified state - Deleted stores/sidebar-store.ts and explorer-sidebar-store.ts - Updated all components to use new panel store - Animation contexts derive isOpen from unified state
This commit is contained in:
@@ -10,11 +10,11 @@ import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
||||
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
||||
import { X, GitBranch, Folder, LayoutGrid, List as ListIcon } from "lucide-react-native";
|
||||
import {
|
||||
useExplorerSidebarStore,
|
||||
usePanelStore,
|
||||
MIN_EXPLORER_SIDEBAR_WIDTH,
|
||||
MAX_EXPLORER_SIDEBAR_WIDTH,
|
||||
type ViewMode,
|
||||
} from "@/stores/explorer-sidebar-store";
|
||||
} from "@/stores/panel-store";
|
||||
import { useExplorerSidebarAnimation } from "@/contexts/explorer-sidebar-animation-context";
|
||||
import { HEADER_INNER_HEIGHT } from "@/constants/layout";
|
||||
import { GitDiffPane } from "./git-diff-pane";
|
||||
@@ -30,8 +30,21 @@ interface ExplorerSidebarProps {
|
||||
export function ExplorerSidebar({ serverId, agentId }: ExplorerSidebarProps) {
|
||||
const { theme } = useUnistyles();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { isOpen, activeTab, width, viewMode, close, setActiveTab, setWidth, setViewMode } =
|
||||
useExplorerSidebarStore();
|
||||
const isMobile =
|
||||
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||
const mobileView = usePanelStore((state) => state.mobileView);
|
||||
const desktopFileExplorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen);
|
||||
const closeToAgent = usePanelStore((state) => state.closeToAgent);
|
||||
const explorerTab = usePanelStore((state) => state.explorerTab);
|
||||
const explorerWidth = usePanelStore((state) => state.explorerWidth);
|
||||
const explorerViewMode = usePanelStore((state) => state.explorerViewMode);
|
||||
const setExplorerTab = usePanelStore((state) => state.setExplorerTab);
|
||||
const setExplorerWidth = usePanelStore((state) => state.setExplorerWidth);
|
||||
const setExplorerViewMode = usePanelStore((state) => state.setExplorerViewMode);
|
||||
|
||||
// Derive isOpen from the unified panel state
|
||||
const isOpen = isMobile ? mobileView === "file-explorer" : desktopFileExplorerOpen;
|
||||
|
||||
const {
|
||||
translateX,
|
||||
backdropOpacity,
|
||||
@@ -42,22 +55,19 @@ export function ExplorerSidebar({ serverId, agentId }: ExplorerSidebarProps) {
|
||||
closeGestureRef,
|
||||
} = useExplorerSidebarAnimation();
|
||||
|
||||
const isMobile =
|
||||
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||
|
||||
// For resize drag, track the starting width
|
||||
const startWidthRef = useRef(width);
|
||||
const resizeWidth = useSharedValue(width);
|
||||
const startWidthRef = useRef(explorerWidth);
|
||||
const resizeWidth = useSharedValue(explorerWidth);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
close();
|
||||
}, [close]);
|
||||
closeToAgent();
|
||||
}, [closeToAgent]);
|
||||
|
||||
const handleTabPress = useCallback(
|
||||
(tab: ExplorerTab) => {
|
||||
setActiveTab(tab);
|
||||
setExplorerTab(tab);
|
||||
},
|
||||
[setActiveTab]
|
||||
[setExplorerTab]
|
||||
);
|
||||
|
||||
// Swipe gesture to close (swipe right on mobile)
|
||||
@@ -116,8 +126,8 @@ export function ExplorerSidebar({ serverId, agentId }: ExplorerSidebarProps) {
|
||||
.enabled(!isMobile)
|
||||
.hitSlop({ left: 8, right: 8, top: 0, bottom: 0 })
|
||||
.onStart(() => {
|
||||
startWidthRef.current = width;
|
||||
resizeWidth.value = width;
|
||||
startWidthRef.current = explorerWidth;
|
||||
resizeWidth.value = explorerWidth;
|
||||
})
|
||||
.onUpdate((event) => {
|
||||
// Dragging left (negative translationX) increases width
|
||||
@@ -129,9 +139,9 @@ export function ExplorerSidebar({ serverId, agentId }: ExplorerSidebarProps) {
|
||||
resizeWidth.value = clampedWidth;
|
||||
})
|
||||
.onEnd(() => {
|
||||
runOnJS(setWidth)(resizeWidth.value);
|
||||
runOnJS(setExplorerWidth)(resizeWidth.value);
|
||||
}),
|
||||
[isMobile, width, resizeWidth, setWidth]
|
||||
[isMobile, explorerWidth, resizeWidth, setExplorerWidth]
|
||||
);
|
||||
|
||||
const sidebarAnimatedStyle = useAnimatedStyle(() => ({
|
||||
@@ -168,13 +178,13 @@ export function ExplorerSidebar({ serverId, agentId }: ExplorerSidebarProps) {
|
||||
pointerEvents="auto"
|
||||
>
|
||||
<SidebarContent
|
||||
activeTab={activeTab}
|
||||
activeTab={explorerTab}
|
||||
onTabPress={handleTabPress}
|
||||
onClose={handleClose}
|
||||
serverId={serverId}
|
||||
agentId={agentId}
|
||||
fileViewMode={viewMode}
|
||||
onFileViewModeChange={setViewMode}
|
||||
fileViewMode={explorerViewMode}
|
||||
onFileViewModeChange={setExplorerViewMode}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
</Animated.View>
|
||||
@@ -201,13 +211,13 @@ export function ExplorerSidebar({ serverId, agentId }: ExplorerSidebarProps) {
|
||||
</GestureDetector>
|
||||
|
||||
<SidebarContent
|
||||
activeTab={activeTab}
|
||||
activeTab={explorerTab}
|
||||
onTabPress={handleTabPress}
|
||||
onClose={handleClose}
|
||||
serverId={serverId}
|
||||
agentId={agentId}
|
||||
fileViewMode={viewMode}
|
||||
onFileViewModeChange={setViewMode}
|
||||
fileViewMode={explorerViewMode}
|
||||
onFileViewModeChange={setExplorerViewMode}
|
||||
isMobile={false}
|
||||
/>
|
||||
</Animated.View>
|
||||
|
||||
Reference in New Issue
Block a user