Extract mobile sidebar and gesture wrappers
This commit is contained in:
@@ -268,23 +268,12 @@ function AppContainer({
|
|||||||
}: AppContainerProps) {
|
}: AppContainerProps) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const daemons = useHosts();
|
const daemons = useHosts();
|
||||||
const mobileView = usePanelStore((state) => state.mobileView);
|
|
||||||
const desktopAgentListOpen = usePanelStore((state) => state.desktop.agentListOpen);
|
|
||||||
const openAgentList = usePanelStore((state) => state.openAgentList);
|
|
||||||
const toggleAgentList = usePanelStore((state) => state.toggleAgentList);
|
const toggleAgentList = usePanelStore((state) => state.toggleAgentList);
|
||||||
const toggleFileExplorer = usePanelStore((state) => state.toggleFileExplorer);
|
const toggleFileExplorer = usePanelStore((state) => state.toggleFileExplorer);
|
||||||
const horizontalScroll = useHorizontalScrollOptional();
|
|
||||||
|
|
||||||
const isMobile =
|
const isMobile =
|
||||||
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||||
const chromeEnabled = chromeEnabledOverride ?? daemons.length > 0;
|
const chromeEnabled = chromeEnabledOverride ?? daemons.length > 0;
|
||||||
const isOpen = chromeEnabled
|
|
||||||
? isMobile
|
|
||||||
? mobileView === "agent-list"
|
|
||||||
: desktopAgentListOpen
|
|
||||||
: false;
|
|
||||||
const openGestureEnabled =
|
|
||||||
chromeEnabled && isMobile && mobileView === "agent";
|
|
||||||
|
|
||||||
useKeyboardShortcuts({
|
useKeyboardShortcuts({
|
||||||
enabled: chromeEnabled,
|
enabled: chromeEnabled,
|
||||||
@@ -293,92 +282,6 @@ function AppContainer({
|
|||||||
selectedAgentId,
|
selectedAgentId,
|
||||||
toggleFileExplorer,
|
toggleFileExplorer,
|
||||||
});
|
});
|
||||||
const {
|
|
||||||
translateX,
|
|
||||||
backdropOpacity,
|
|
||||||
windowWidth,
|
|
||||||
animateToOpen,
|
|
||||||
animateToClose,
|
|
||||||
isGesturing,
|
|
||||||
} = useSidebarAnimation();
|
|
||||||
|
|
||||||
// Track initial touch position for manual activation
|
|
||||||
const touchStartX = useSharedValue(0);
|
|
||||||
|
|
||||||
// Open gesture: swipe right from anywhere to open sidebar (interactive drag)
|
|
||||||
// If any horizontal scroll is scrolled right, let the scroll view handle the gesture first
|
|
||||||
const openGesture = useMemo(
|
|
||||||
() =>
|
|
||||||
Gesture.Pan()
|
|
||||||
.enabled(openGestureEnabled)
|
|
||||||
.manualActivation(true)
|
|
||||||
// Fail if 10px vertical movement happens first (allow vertical scroll)
|
|
||||||
.failOffsetY([-10, 10])
|
|
||||||
.onTouchesDown((event) => {
|
|
||||||
const touch = event.changedTouches[0];
|
|
||||||
if (touch) {
|
|
||||||
touchStartX.value = touch.absoluteX;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.onTouchesMove((event, stateManager) => {
|
|
||||||
const touch = event.changedTouches[0];
|
|
||||||
if (!touch || event.numberOfTouches !== 1) return;
|
|
||||||
|
|
||||||
const deltaX = touch.absoluteX - touchStartX.value;
|
|
||||||
|
|
||||||
// If horizontal scroll is scrolled right, fail so ScrollView handles it
|
|
||||||
if (horizontalScroll?.isAnyScrolledRight.value) {
|
|
||||||
stateManager.fail();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Activate after 15px rightward movement
|
|
||||||
if (deltaX > 15) {
|
|
||||||
stateManager.activate();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.onStart(() => {
|
|
||||||
isGesturing.value = true;
|
|
||||||
})
|
|
||||||
.onUpdate((event) => {
|
|
||||||
// Start from closed position (-windowWidth) and move towards 0
|
|
||||||
const newTranslateX = Math.min(0, -windowWidth + event.translationX);
|
|
||||||
translateX.value = newTranslateX;
|
|
||||||
backdropOpacity.value = interpolate(
|
|
||||||
newTranslateX,
|
|
||||||
[-windowWidth, 0],
|
|
||||||
[0, 1],
|
|
||||||
Extrapolation.CLAMP
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.onEnd((event) => {
|
|
||||||
isGesturing.value = false;
|
|
||||||
// Open if dragged more than 1/3 of sidebar or fast swipe
|
|
||||||
const shouldOpen = event.translationX > windowWidth / 3 || event.velocityX > 500;
|
|
||||||
if (shouldOpen) {
|
|
||||||
animateToOpen();
|
|
||||||
runOnJS(openAgentList)();
|
|
||||||
} else {
|
|
||||||
animateToClose();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.onFinalize(() => {
|
|
||||||
isGesturing.value = false;
|
|
||||||
}),
|
|
||||||
[
|
|
||||||
openGestureEnabled,
|
|
||||||
windowWidth,
|
|
||||||
translateX,
|
|
||||||
backdropOpacity,
|
|
||||||
animateToOpen,
|
|
||||||
animateToClose,
|
|
||||||
openAgentList,
|
|
||||||
mobileView,
|
|
||||||
isGesturing,
|
|
||||||
horizontalScroll?.isAnyScrolledRight,
|
|
||||||
touchStartX,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
const containerStyle = useMemo(
|
const containerStyle = useMemo(
|
||||||
() => ({ flex: 1 as const, backgroundColor: theme.colors.surface0 }),
|
() => ({ flex: 1 as const, backgroundColor: theme.colors.surface0 }),
|
||||||
@@ -407,8 +310,103 @@ function AppContainer({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GestureDetector gesture={openGesture} touchAction="pan-y">
|
<MobileGestureWrapper chromeEnabled={chromeEnabled}>
|
||||||
{content}
|
{content}
|
||||||
|
</MobileGestureWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MobileGestureWrapper({
|
||||||
|
children,
|
||||||
|
chromeEnabled,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
chromeEnabled: boolean;
|
||||||
|
}) {
|
||||||
|
const mobileView = usePanelStore((state) => state.mobileView);
|
||||||
|
const openAgentList = usePanelStore((state) => state.openAgentList);
|
||||||
|
const horizontalScroll = useHorizontalScrollOptional();
|
||||||
|
const {
|
||||||
|
translateX,
|
||||||
|
backdropOpacity,
|
||||||
|
windowWidth,
|
||||||
|
animateToOpen,
|
||||||
|
animateToClose,
|
||||||
|
isGesturing,
|
||||||
|
} = useSidebarAnimation();
|
||||||
|
const touchStartX = useSharedValue(0);
|
||||||
|
const openGestureEnabled = chromeEnabled && mobileView === "agent";
|
||||||
|
|
||||||
|
const openGesture = useMemo(
|
||||||
|
() =>
|
||||||
|
Gesture.Pan()
|
||||||
|
.enabled(openGestureEnabled)
|
||||||
|
.manualActivation(true)
|
||||||
|
.failOffsetY([-10, 10])
|
||||||
|
.onTouchesDown((event) => {
|
||||||
|
const touch = event.changedTouches[0];
|
||||||
|
if (touch) {
|
||||||
|
touchStartX.value = touch.absoluteX;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onTouchesMove((event, stateManager) => {
|
||||||
|
const touch = event.changedTouches[0];
|
||||||
|
if (!touch || event.numberOfTouches !== 1) return;
|
||||||
|
|
||||||
|
const deltaX = touch.absoluteX - touchStartX.value;
|
||||||
|
|
||||||
|
if (horizontalScroll?.isAnyScrolledRight.value) {
|
||||||
|
stateManager.fail();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deltaX > 15) {
|
||||||
|
stateManager.activate();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onStart(() => {
|
||||||
|
isGesturing.value = true;
|
||||||
|
})
|
||||||
|
.onUpdate((event) => {
|
||||||
|
const newTranslateX = Math.min(0, -windowWidth + event.translationX);
|
||||||
|
translateX.value = newTranslateX;
|
||||||
|
backdropOpacity.value = interpolate(
|
||||||
|
newTranslateX,
|
||||||
|
[-windowWidth, 0],
|
||||||
|
[0, 1],
|
||||||
|
Extrapolation.CLAMP
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.onEnd((event) => {
|
||||||
|
isGesturing.value = false;
|
||||||
|
const shouldOpen = event.translationX > windowWidth / 3 || event.velocityX > 500;
|
||||||
|
if (shouldOpen) {
|
||||||
|
animateToOpen();
|
||||||
|
runOnJS(openAgentList)();
|
||||||
|
} else {
|
||||||
|
animateToClose();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onFinalize(() => {
|
||||||
|
isGesturing.value = false;
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
openGestureEnabled,
|
||||||
|
windowWidth,
|
||||||
|
translateX,
|
||||||
|
backdropOpacity,
|
||||||
|
animateToOpen,
|
||||||
|
animateToClose,
|
||||||
|
openAgentList,
|
||||||
|
isGesturing,
|
||||||
|
horizontalScroll?.isAnyScrolledRight,
|
||||||
|
touchStartX,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GestureDetector gesture={openGesture} touchAction="pan-y">
|
||||||
|
{children}
|
||||||
</GestureDetector>
|
</GestureDetector>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,15 @@
|
|||||||
import { memo, useCallback, useMemo, useState, useEffect, useRef, useSyncExternalStore } from 'react'
|
import {
|
||||||
|
memo,
|
||||||
|
useCallback,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useSyncExternalStore,
|
||||||
|
type Dispatch,
|
||||||
|
type RefObject,
|
||||||
|
type SetStateAction,
|
||||||
|
} from 'react'
|
||||||
import { View, Pressable, Text, Platform } from 'react-native'
|
import { View, Pressable, Text, Platform } from 'react-native'
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||||
import Animated, {
|
import Animated, {
|
||||||
@@ -18,7 +29,7 @@ import { usePanelStore } from '@/stores/panel-store'
|
|||||||
import { SidebarWorkspaceList } from './sidebar-workspace-list'
|
import { SidebarWorkspaceList } from './sidebar-workspace-list'
|
||||||
import { SidebarAgentListSkeleton } from './sidebar-agent-list-skeleton'
|
import { SidebarAgentListSkeleton } from './sidebar-agent-list-skeleton'
|
||||||
import { useSidebarShortcutModel } from '@/hooks/use-sidebar-shortcut-model'
|
import { useSidebarShortcutModel } from '@/hooks/use-sidebar-shortcut-model'
|
||||||
import { useSidebarWorkspacesList } from '@/hooks/use-sidebar-workspaces-list'
|
import { useSidebarWorkspacesList, type SidebarProjectEntry } from '@/hooks/use-sidebar-workspaces-list'
|
||||||
import { useSidebarAnimation } from '@/contexts/sidebar-animation-context'
|
import { useSidebarAnimation } from '@/contexts/sidebar-animation-context'
|
||||||
import { useTauriDragHandlers, useTrafficLightPadding } from '@/utils/tauri-window'
|
import { useTauriDragHandlers, useTrafficLightPadding } from '@/utils/tauri-window'
|
||||||
import { Combobox } from '@/components/ui/combobox'
|
import { Combobox } from '@/components/ui/combobox'
|
||||||
@@ -34,12 +45,58 @@ import {
|
|||||||
import { useOpenProjectPicker } from '@/hooks/use-open-project-picker'
|
import { useOpenProjectPicker } from '@/hooks/use-open-project-picker'
|
||||||
|
|
||||||
const DESKTOP_SIDEBAR_WIDTH = 320
|
const DESKTOP_SIDEBAR_WIDTH = 320
|
||||||
|
type SidebarShortcutModel = ReturnType<typeof useSidebarShortcutModel>
|
||||||
|
type SidebarTheme = ReturnType<typeof useUnistyles>['theme']
|
||||||
|
|
||||||
interface LeftSidebarProps {
|
interface LeftSidebarProps {
|
||||||
selectedAgentId?: string
|
selectedAgentId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface HostOption {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SidebarSharedProps {
|
||||||
|
theme: SidebarTheme
|
||||||
|
activeServerId: string | null
|
||||||
|
activeHostLabel: string
|
||||||
|
activeHostStatusColor: string
|
||||||
|
hostOptions: HostOption[]
|
||||||
|
hostTriggerRef: RefObject<View | null>
|
||||||
|
isHostPickerOpen: boolean
|
||||||
|
setIsHostPickerOpen: Dispatch<SetStateAction<boolean>>
|
||||||
|
projects: SidebarProjectEntry[]
|
||||||
|
isInitialLoad: boolean
|
||||||
|
isRevalidating: boolean
|
||||||
|
isManualRefresh: boolean
|
||||||
|
collapsedProjectKeys: SidebarShortcutModel['collapsedProjectKeys']
|
||||||
|
shortcutIndexByWorkspaceKey: SidebarShortcutModel['shortcutIndexByWorkspaceKey']
|
||||||
|
toggleProjectCollapsed: SidebarShortcutModel['toggleProjectCollapsed']
|
||||||
|
setProjectCollapsed: SidebarShortcutModel['setProjectCollapsed']
|
||||||
|
handleRefresh: () => void
|
||||||
|
handleHostSelect: (nextServerId: string) => void
|
||||||
|
handleOpenProject: () => void
|
||||||
|
handleSettings: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MobileSidebarProps extends SidebarSharedProps {
|
||||||
|
insetsTop: number
|
||||||
|
insetsBottom: number
|
||||||
|
isOpen: boolean
|
||||||
|
closeToAgent: () => void
|
||||||
|
handleViewMoreNavigate: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DesktopSidebarProps extends SidebarSharedProps {
|
||||||
|
isOpen: boolean
|
||||||
|
handleViewMore: () => void
|
||||||
|
}
|
||||||
|
|
||||||
export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selectedAgentId }: LeftSidebarProps) {
|
export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selectedAgentId }: LeftSidebarProps) {
|
||||||
|
void _selectedAgentId
|
||||||
|
|
||||||
const { theme } = useUnistyles()
|
const { theme } = useUnistyles()
|
||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
const isMobile = UnistylesRuntime.breakpoint === 'xs' || UnistylesRuntime.breakpoint === 'sm'
|
const isMobile = UnistylesRuntime.breakpoint === 'xs' || UnistylesRuntime.breakpoint === 'sm'
|
||||||
@@ -109,10 +166,9 @@ export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selecte
|
|||||||
})),
|
})),
|
||||||
[daemons, runtime, runtimeConnectionStatusSignature]
|
[daemons, runtime, runtimeConnectionStatusSignature]
|
||||||
)
|
)
|
||||||
const hostTriggerRef = useRef<View>(null)
|
const hostTriggerRef = useRef<View | null>(null)
|
||||||
const [isHostPickerOpen, setIsHostPickerOpen] = useState(false)
|
const [isHostPickerOpen, setIsHostPickerOpen] = useState(false)
|
||||||
|
|
||||||
// Derive isOpen from the unified panel state
|
|
||||||
const isOpen = isMobile ? mobileView === 'agent-list' : desktopAgentListOpen
|
const isOpen = isMobile ? mobileView === 'agent-list' : desktopAgentListOpen
|
||||||
|
|
||||||
const { projects, isInitialLoad, isRevalidating, refreshAll } = useSidebarWorkspacesList({
|
const { projects, isInitialLoad, isRevalidating, refreshAll } = useSidebarWorkspacesList({
|
||||||
@@ -121,21 +177,7 @@ export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selecte
|
|||||||
})
|
})
|
||||||
const { collapsedProjectKeys, shortcutIndexByWorkspaceKey, toggleProjectCollapsed, setProjectCollapsed } =
|
const { collapsedProjectKeys, shortcutIndexByWorkspaceKey, toggleProjectCollapsed, setProjectCollapsed } =
|
||||||
useSidebarShortcutModel(projects)
|
useSidebarShortcutModel(projects)
|
||||||
const {
|
|
||||||
translateX,
|
|
||||||
backdropOpacity,
|
|
||||||
windowWidth,
|
|
||||||
animateToOpen,
|
|
||||||
animateToClose,
|
|
||||||
isGesturing,
|
|
||||||
closeGestureRef,
|
|
||||||
} = useSidebarAnimation()
|
|
||||||
const dragHandlers = useTauriDragHandlers()
|
|
||||||
const trafficLightPadding = useTrafficLightPadding()
|
|
||||||
const closeTouchStartX = useSharedValue(0)
|
|
||||||
const closeTouchStartY = useSharedValue(0)
|
|
||||||
|
|
||||||
// Track user-initiated refresh to avoid showing spinner on background revalidation
|
|
||||||
const [isManualRefresh, setIsManualRefresh] = useState(false)
|
const [isManualRefresh, setIsManualRefresh] = useState(false)
|
||||||
|
|
||||||
const handleRefresh = useCallback(() => {
|
const handleRefresh = useCallback(() => {
|
||||||
@@ -143,17 +185,12 @@ export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selecte
|
|||||||
refreshAll()
|
refreshAll()
|
||||||
}, [refreshAll])
|
}, [refreshAll])
|
||||||
|
|
||||||
// Reset manual refresh flag when revalidation completes
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isRevalidating && isManualRefresh) {
|
if (!isRevalidating && isManualRefresh) {
|
||||||
setIsManualRefresh(false)
|
setIsManualRefresh(false)
|
||||||
}
|
}
|
||||||
}, [isRevalidating, isManualRefresh])
|
}, [isRevalidating, isManualRefresh])
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
|
||||||
closeToAgent()
|
|
||||||
}, [closeToAgent])
|
|
||||||
|
|
||||||
const openProjectPicker = useOpenProjectPicker(activeServerId)
|
const openProjectPicker = useOpenProjectPicker(activeServerId)
|
||||||
|
|
||||||
const handleOpenProjectMobile = useCallback(() => {
|
const handleOpenProjectMobile = useCallback(() => {
|
||||||
@@ -165,7 +202,6 @@ export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selecte
|
|||||||
void openProjectPicker()
|
void openProjectPicker()
|
||||||
}, [openProjectPicker])
|
}, [openProjectPicker])
|
||||||
|
|
||||||
// Mobile: close sidebar and navigate
|
|
||||||
const handleSettingsMobile = useCallback(() => {
|
const handleSettingsMobile = useCallback(() => {
|
||||||
if (!activeServerId) {
|
if (!activeServerId) {
|
||||||
return
|
return
|
||||||
@@ -174,7 +210,6 @@ export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selecte
|
|||||||
router.push(buildHostSettingsRoute(activeServerId) as any)
|
router.push(buildHostSettingsRoute(activeServerId) as any)
|
||||||
}, [activeServerId, closeToAgent])
|
}, [activeServerId, closeToAgent])
|
||||||
|
|
||||||
// Desktop: just navigate, don't close
|
|
||||||
const handleSettingsDesktop = useCallback(() => {
|
const handleSettingsDesktop = useCallback(() => {
|
||||||
if (!activeServerId) {
|
if (!activeServerId) {
|
||||||
return
|
return
|
||||||
@@ -182,17 +217,12 @@ export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selecte
|
|||||||
router.push(buildHostSettingsRoute(activeServerId) as any)
|
router.push(buildHostSettingsRoute(activeServerId) as any)
|
||||||
}, [activeServerId])
|
}, [activeServerId])
|
||||||
|
|
||||||
const handleViewMore = useCallback(() => {
|
const handleViewMoreNavigate = useCallback(() => {
|
||||||
if (!activeServerId) {
|
if (!activeServerId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (isMobile) {
|
|
||||||
translateX.value = -windowWidth
|
|
||||||
backdropOpacity.value = 0
|
|
||||||
closeToAgent()
|
|
||||||
}
|
|
||||||
router.push(buildHostAgentsRoute(activeServerId) as any)
|
router.push(buildHostAgentsRoute(activeServerId) as any)
|
||||||
}, [activeServerId, backdropOpacity, closeToAgent, isMobile, translateX, windowWidth])
|
}, [activeServerId])
|
||||||
|
|
||||||
const handleHostSelect = useCallback(
|
const handleHostSelect = useCallback(
|
||||||
(nextServerId: string) => {
|
(nextServerId: string) => {
|
||||||
@@ -206,14 +236,118 @@ export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selecte
|
|||||||
[pathname]
|
[pathname]
|
||||||
)
|
)
|
||||||
|
|
||||||
// Close gesture (swipe left to close when sidebar is open)
|
const sharedProps = {
|
||||||
|
theme,
|
||||||
|
activeServerId,
|
||||||
|
activeHostLabel,
|
||||||
|
activeHostStatusColor,
|
||||||
|
hostOptions,
|
||||||
|
hostTriggerRef,
|
||||||
|
isHostPickerOpen,
|
||||||
|
setIsHostPickerOpen,
|
||||||
|
projects,
|
||||||
|
isInitialLoad,
|
||||||
|
isRevalidating,
|
||||||
|
isManualRefresh,
|
||||||
|
collapsedProjectKeys,
|
||||||
|
shortcutIndexByWorkspaceKey,
|
||||||
|
toggleProjectCollapsed,
|
||||||
|
setProjectCollapsed,
|
||||||
|
handleRefresh,
|
||||||
|
handleHostSelect,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
return (
|
||||||
|
<MobileSidebar
|
||||||
|
{...sharedProps}
|
||||||
|
insetsTop={insets.top}
|
||||||
|
insetsBottom={insets.bottom}
|
||||||
|
isOpen={isOpen}
|
||||||
|
closeToAgent={closeToAgent}
|
||||||
|
handleOpenProject={handleOpenProjectMobile}
|
||||||
|
handleSettings={handleSettingsMobile}
|
||||||
|
handleViewMoreNavigate={handleViewMoreNavigate}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DesktopSidebar
|
||||||
|
{...sharedProps}
|
||||||
|
isOpen={isOpen}
|
||||||
|
handleOpenProject={handleOpenProjectDesktop}
|
||||||
|
handleSettings={handleSettingsDesktop}
|
||||||
|
handleViewMore={handleViewMoreNavigate}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
function MobileSidebar({
|
||||||
|
theme,
|
||||||
|
activeServerId,
|
||||||
|
activeHostLabel,
|
||||||
|
activeHostStatusColor,
|
||||||
|
hostOptions,
|
||||||
|
hostTriggerRef,
|
||||||
|
isHostPickerOpen,
|
||||||
|
setIsHostPickerOpen,
|
||||||
|
projects,
|
||||||
|
isInitialLoad,
|
||||||
|
isRevalidating,
|
||||||
|
isManualRefresh,
|
||||||
|
collapsedProjectKeys,
|
||||||
|
shortcutIndexByWorkspaceKey,
|
||||||
|
toggleProjectCollapsed,
|
||||||
|
setProjectCollapsed,
|
||||||
|
handleRefresh,
|
||||||
|
handleHostSelect,
|
||||||
|
handleOpenProject,
|
||||||
|
handleSettings,
|
||||||
|
insetsTop,
|
||||||
|
insetsBottom,
|
||||||
|
isOpen,
|
||||||
|
closeToAgent,
|
||||||
|
handleViewMoreNavigate,
|
||||||
|
}: MobileSidebarProps) {
|
||||||
|
const {
|
||||||
|
translateX,
|
||||||
|
backdropOpacity,
|
||||||
|
windowWidth,
|
||||||
|
animateToOpen,
|
||||||
|
animateToClose,
|
||||||
|
isGesturing,
|
||||||
|
closeGestureRef,
|
||||||
|
} = useSidebarAnimation()
|
||||||
|
const closeTouchStartX = useSharedValue(0)
|
||||||
|
const closeTouchStartY = useSharedValue(0)
|
||||||
|
|
||||||
|
const handleClose = useCallback(() => {
|
||||||
|
closeToAgent()
|
||||||
|
}, [closeToAgent])
|
||||||
|
|
||||||
|
const handleViewMore = useCallback(() => {
|
||||||
|
if (!activeServerId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
translateX.value = -windowWidth
|
||||||
|
backdropOpacity.value = 0
|
||||||
|
closeToAgent()
|
||||||
|
handleViewMoreNavigate()
|
||||||
|
}, [
|
||||||
|
activeServerId,
|
||||||
|
backdropOpacity,
|
||||||
|
closeToAgent,
|
||||||
|
handleViewMoreNavigate,
|
||||||
|
translateX,
|
||||||
|
windowWidth,
|
||||||
|
])
|
||||||
|
|
||||||
const closeGesture = useMemo(
|
const closeGesture = useMemo(
|
||||||
() =>
|
() =>
|
||||||
Gesture.Pan()
|
Gesture.Pan()
|
||||||
.withRef(closeGestureRef)
|
.withRef(closeGestureRef)
|
||||||
.enabled(isOpen)
|
.enabled(isOpen)
|
||||||
// Use manual activation so child views keep touch streams unless we detect
|
|
||||||
// an intentional left-swipe close (mirrors explorer-sidebar pattern).
|
|
||||||
.manualActivation(true)
|
.manualActivation(true)
|
||||||
.onTouchesDown((event) => {
|
.onTouchesDown((event) => {
|
||||||
const touch = event.changedTouches[0]
|
const touch = event.changedTouches[0]
|
||||||
@@ -235,7 +369,6 @@ export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selecte
|
|||||||
const absDeltaX = Math.abs(deltaX)
|
const absDeltaX = Math.abs(deltaX)
|
||||||
const absDeltaY = Math.abs(deltaY)
|
const absDeltaY = Math.abs(deltaY)
|
||||||
|
|
||||||
// Fail quickly on clear rightward or vertical intent so child views keep control.
|
|
||||||
if (deltaX >= 10) {
|
if (deltaX >= 10) {
|
||||||
stateManager.fail()
|
stateManager.fail()
|
||||||
return
|
return
|
||||||
@@ -244,8 +377,6 @@ export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selecte
|
|||||||
stateManager.fail()
|
stateManager.fail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activate only on intentional leftward movement.
|
|
||||||
if (deltaX <= -15 && absDeltaX > absDeltaY) {
|
if (deltaX <= -15 && absDeltaX > absDeltaY) {
|
||||||
stateManager.activate()
|
stateManager.activate()
|
||||||
}
|
}
|
||||||
@@ -254,8 +385,6 @@ export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selecte
|
|||||||
isGesturing.value = true
|
isGesturing.value = true
|
||||||
})
|
})
|
||||||
.onUpdate((event) => {
|
.onUpdate((event) => {
|
||||||
if (!isMobile) return
|
|
||||||
// Only allow swiping left (closing)
|
|
||||||
const newTranslateX = Math.min(0, Math.max(-windowWidth, event.translationX))
|
const newTranslateX = Math.min(0, Math.max(-windowWidth, event.translationX))
|
||||||
translateX.value = newTranslateX
|
translateX.value = newTranslateX
|
||||||
backdropOpacity.value = interpolate(
|
backdropOpacity.value = interpolate(
|
||||||
@@ -267,7 +396,6 @@ export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selecte
|
|||||||
})
|
})
|
||||||
.onEnd((event) => {
|
.onEnd((event) => {
|
||||||
isGesturing.value = false
|
isGesturing.value = false
|
||||||
if (!isMobile) return
|
|
||||||
const shouldClose = event.translationX < -windowWidth / 3 || event.velocityX < -500
|
const shouldClose = event.translationX < -windowWidth / 3 || event.velocityX < -500
|
||||||
if (shouldClose) {
|
if (shouldClose) {
|
||||||
animateToClose()
|
animateToClose()
|
||||||
@@ -285,7 +413,6 @@ export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selecte
|
|||||||
closeTouchStartX,
|
closeTouchStartX,
|
||||||
closeTouchStartY,
|
closeTouchStartY,
|
||||||
isGesturing,
|
isGesturing,
|
||||||
isMobile,
|
|
||||||
windowWidth,
|
windowWidth,
|
||||||
translateX,
|
translateX,
|
||||||
backdropOpacity,
|
backdropOpacity,
|
||||||
@@ -296,8 +423,8 @@ export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selecte
|
|||||||
)
|
)
|
||||||
|
|
||||||
const mobileSidebarInsetStyle = useMemo(
|
const mobileSidebarInsetStyle = useMemo(
|
||||||
() => ({ width: windowWidth, paddingTop: insets.top, paddingBottom: insets.bottom }),
|
() => ({ width: windowWidth, paddingTop: insetsTop, paddingBottom: insetsBottom }),
|
||||||
[windowWidth, insets.top, insets.bottom]
|
[windowWidth, insetsTop, insetsBottom]
|
||||||
)
|
)
|
||||||
|
|
||||||
const hostStatusDotStyle = useMemo(
|
const hostStatusDotStyle = useMemo(
|
||||||
@@ -314,160 +441,174 @@ export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selecte
|
|||||||
pointerEvents: backdropOpacity.value > 0.01 ? 'auto' : 'none',
|
pointerEvents: backdropOpacity.value > 0.01 ? 'auto' : 'none',
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Render mobile sidebar
|
|
||||||
// On web, keep the overlay interactive only while the sidebar is open.
|
|
||||||
// This preserves swipe/scroll behavior without blocking taps when closed.
|
|
||||||
const overlayPointerEvents = Platform.OS === 'web' ? (isOpen ? 'auto' : 'none') : 'box-none'
|
const overlayPointerEvents = Platform.OS === 'web' ? (isOpen ? 'auto' : 'none') : 'box-none'
|
||||||
if (isMobile) {
|
|
||||||
return (
|
|
||||||
<View style={StyleSheet.absoluteFillObject} pointerEvents={overlayPointerEvents}>
|
|
||||||
{/* Backdrop */}
|
|
||||||
<Animated.View style={[styles.backdrop, backdropAnimatedStyle]}>
|
|
||||||
<Pressable style={styles.backdropPressable} onPress={handleClose} />
|
|
||||||
</Animated.View>
|
|
||||||
|
|
||||||
<GestureDetector gesture={closeGesture} touchAction="pan-y">
|
return (
|
||||||
<Animated.View
|
<View style={StyleSheet.absoluteFillObject} pointerEvents={overlayPointerEvents}>
|
||||||
style={[
|
<Animated.View style={[styles.backdrop, backdropAnimatedStyle]}>
|
||||||
styles.mobileSidebar,
|
<Pressable style={styles.backdropPressable} onPress={handleClose} />
|
||||||
mobileSidebarInsetStyle,
|
</Animated.View>
|
||||||
sidebarAnimatedStyle,
|
|
||||||
]}
|
|
||||||
pointerEvents="auto"
|
|
||||||
>
|
|
||||||
<View style={styles.sidebarContent} pointerEvents="auto">
|
|
||||||
{/* Header */}
|
|
||||||
<View style={styles.sidebarHeader}>
|
|
||||||
<View style={styles.sidebarHeaderRow}>
|
|
||||||
<Pressable
|
|
||||||
style={styles.newAgentButton}
|
|
||||||
testID="sidebar-sessions"
|
|
||||||
onPress={handleViewMore}
|
|
||||||
>
|
|
||||||
{({ hovered }) => (
|
|
||||||
<>
|
|
||||||
<MessagesSquare
|
|
||||||
size={theme.iconSize.md}
|
|
||||||
color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted}
|
|
||||||
/>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
styles.newAgentButtonText,
|
|
||||||
hovered && styles.newAgentButtonTextHovered,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
Sessions
|
|
||||||
</Text>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Pressable>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Middle: scrollable project/workspace tree */}
|
<GestureDetector gesture={closeGesture} touchAction="pan-y">
|
||||||
{isInitialLoad ? (
|
<Animated.View
|
||||||
<SidebarAgentListSkeleton />
|
style={[styles.mobileSidebar, mobileSidebarInsetStyle, sidebarAnimatedStyle]}
|
||||||
) : (
|
pointerEvents="auto"
|
||||||
<SidebarWorkspaceList
|
>
|
||||||
serverId={activeServerId}
|
<View style={styles.sidebarContent} pointerEvents="auto">
|
||||||
collapsedProjectKeys={collapsedProjectKeys}
|
<View style={styles.sidebarHeader}>
|
||||||
onToggleProjectCollapsed={toggleProjectCollapsed}
|
<View style={styles.sidebarHeaderRow}>
|
||||||
onSetProjectCollapsed={setProjectCollapsed}
|
<Pressable
|
||||||
shortcutIndexByWorkspaceKey={shortcutIndexByWorkspaceKey}
|
style={styles.newAgentButton}
|
||||||
projects={projects}
|
testID="sidebar-sessions"
|
||||||
isRefreshing={isManualRefresh && isRevalidating}
|
onPress={handleViewMore}
|
||||||
onRefresh={handleRefresh}
|
>
|
||||||
onWorkspacePress={closeToAgent}
|
{({ hovered }) => (
|
||||||
parentGestureRef={closeGestureRef}
|
<>
|
||||||
/>
|
<MessagesSquare
|
||||||
)}
|
size={theme.iconSize.md}
|
||||||
|
|
||||||
{/* Footer */}
|
|
||||||
<View style={styles.sidebarFooter}>
|
|
||||||
<View style={styles.footerHostSlot}>
|
|
||||||
<Pressable
|
|
||||||
ref={hostTriggerRef}
|
|
||||||
style={({ hovered = false }) => [
|
|
||||||
styles.hostTrigger,
|
|
||||||
hovered && styles.hostTriggerHovered,
|
|
||||||
]}
|
|
||||||
onPress={() => setIsHostPickerOpen(true)}
|
|
||||||
disabled={hostOptions.length === 0}
|
|
||||||
>
|
|
||||||
<View
|
|
||||||
style={[styles.hostStatusDot, { backgroundColor: activeHostStatusColor }]}
|
|
||||||
/>
|
|
||||||
<Text style={styles.hostTriggerText} numberOfLines={1}>
|
|
||||||
{activeHostLabel}
|
|
||||||
</Text>
|
|
||||||
</Pressable>
|
|
||||||
</View>
|
|
||||||
<View style={styles.footerIconRow}>
|
|
||||||
<Tooltip delayDuration={300}>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Pressable
|
|
||||||
style={styles.footerIconButton}
|
|
||||||
testID="sidebar-add-project"
|
|
||||||
nativeID="sidebar-add-project"
|
|
||||||
collapsable={false}
|
|
||||||
accessible
|
|
||||||
accessibilityLabel="Add project"
|
|
||||||
accessibilityRole="button"
|
|
||||||
onPress={handleOpenProjectMobile}
|
|
||||||
>
|
|
||||||
{({ hovered }) => (
|
|
||||||
<Plus
|
|
||||||
size={theme.iconSize.lg}
|
|
||||||
color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Pressable>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="top" align="center" offset={8}>
|
|
||||||
<View style={styles.tooltipRow}>
|
|
||||||
<Text style={styles.tooltipText}>Add project</Text>
|
|
||||||
<Shortcut keys={['⌘', '⇧', 'O']} />
|
|
||||||
</View>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
<Pressable
|
|
||||||
style={styles.footerIconButton}
|
|
||||||
testID="sidebar-settings"
|
|
||||||
nativeID="sidebar-settings"
|
|
||||||
collapsable={false}
|
|
||||||
accessible
|
|
||||||
accessibilityLabel="Settings"
|
|
||||||
accessibilityRole="button"
|
|
||||||
onPress={handleSettingsMobile}
|
|
||||||
>
|
|
||||||
{({ hovered }) => (
|
|
||||||
<Settings
|
|
||||||
size={theme.iconSize.lg}
|
|
||||||
color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted}
|
color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted}
|
||||||
/>
|
/>
|
||||||
)}
|
<Text
|
||||||
</Pressable>
|
style={[styles.newAgentButtonText, hovered && styles.newAgentButtonTextHovered]}
|
||||||
</View>
|
>
|
||||||
<Combobox
|
Sessions
|
||||||
options={hostOptions}
|
</Text>
|
||||||
value={activeServerId ?? ''}
|
</>
|
||||||
onSelect={handleHostSelect}
|
)}
|
||||||
searchable={false}
|
</Pressable>
|
||||||
title="Switch host"
|
|
||||||
searchPlaceholder="Search hosts..."
|
|
||||||
open={isHostPickerOpen}
|
|
||||||
onOpenChange={setIsHostPickerOpen}
|
|
||||||
anchorRef={hostTriggerRef}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</Animated.View>
|
|
||||||
</GestureDetector>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Desktop: no edge swipe, just show/hide based on isOpen
|
{isInitialLoad ? (
|
||||||
|
<SidebarAgentListSkeleton />
|
||||||
|
) : (
|
||||||
|
<SidebarWorkspaceList
|
||||||
|
serverId={activeServerId}
|
||||||
|
collapsedProjectKeys={collapsedProjectKeys}
|
||||||
|
onToggleProjectCollapsed={toggleProjectCollapsed}
|
||||||
|
onSetProjectCollapsed={setProjectCollapsed}
|
||||||
|
shortcutIndexByWorkspaceKey={shortcutIndexByWorkspaceKey}
|
||||||
|
projects={projects}
|
||||||
|
isRefreshing={isManualRefresh && isRevalidating}
|
||||||
|
onRefresh={handleRefresh}
|
||||||
|
onWorkspacePress={closeToAgent}
|
||||||
|
parentGestureRef={closeGestureRef}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<View style={styles.sidebarFooter}>
|
||||||
|
<View style={styles.footerHostSlot}>
|
||||||
|
<Pressable
|
||||||
|
ref={hostTriggerRef}
|
||||||
|
style={({ hovered = false }) => [
|
||||||
|
styles.hostTrigger,
|
||||||
|
hovered && styles.hostTriggerHovered,
|
||||||
|
]}
|
||||||
|
onPress={() => setIsHostPickerOpen(true)}
|
||||||
|
disabled={hostOptions.length === 0}
|
||||||
|
>
|
||||||
|
<View style={hostStatusDotStyle} />
|
||||||
|
<Text style={styles.hostTriggerText} numberOfLines={1}>
|
||||||
|
{activeHostLabel}
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
<View style={styles.footerIconRow}>
|
||||||
|
<Tooltip delayDuration={300}>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Pressable
|
||||||
|
style={styles.footerIconButton}
|
||||||
|
testID="sidebar-add-project"
|
||||||
|
nativeID="sidebar-add-project"
|
||||||
|
collapsable={false}
|
||||||
|
accessible
|
||||||
|
accessibilityLabel="Add project"
|
||||||
|
accessibilityRole="button"
|
||||||
|
onPress={handleOpenProject}
|
||||||
|
>
|
||||||
|
{({ hovered }) => (
|
||||||
|
<Plus
|
||||||
|
size={theme.iconSize.lg}
|
||||||
|
color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Pressable>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="top" align="center" offset={8}>
|
||||||
|
<View style={styles.tooltipRow}>
|
||||||
|
<Text style={styles.tooltipText}>Add project</Text>
|
||||||
|
<Shortcut keys={['⌘', '⇧', 'O']} />
|
||||||
|
</View>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
<Pressable
|
||||||
|
style={styles.footerIconButton}
|
||||||
|
testID="sidebar-settings"
|
||||||
|
nativeID="sidebar-settings"
|
||||||
|
collapsable={false}
|
||||||
|
accessible
|
||||||
|
accessibilityLabel="Settings"
|
||||||
|
accessibilityRole="button"
|
||||||
|
onPress={handleSettings}
|
||||||
|
>
|
||||||
|
{({ hovered }) => (
|
||||||
|
<Settings
|
||||||
|
size={theme.iconSize.lg}
|
||||||
|
color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
<Combobox
|
||||||
|
options={hostOptions}
|
||||||
|
value={activeServerId ?? ''}
|
||||||
|
onSelect={handleHostSelect}
|
||||||
|
searchable={false}
|
||||||
|
title="Switch host"
|
||||||
|
searchPlaceholder="Search hosts..."
|
||||||
|
open={isHostPickerOpen}
|
||||||
|
onOpenChange={setIsHostPickerOpen}
|
||||||
|
anchorRef={hostTriggerRef}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
</GestureDetector>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DesktopSidebar({
|
||||||
|
theme,
|
||||||
|
activeServerId,
|
||||||
|
activeHostLabel,
|
||||||
|
activeHostStatusColor,
|
||||||
|
hostOptions,
|
||||||
|
hostTriggerRef,
|
||||||
|
isHostPickerOpen,
|
||||||
|
setIsHostPickerOpen,
|
||||||
|
projects,
|
||||||
|
isInitialLoad,
|
||||||
|
isRevalidating,
|
||||||
|
isManualRefresh,
|
||||||
|
collapsedProjectKeys,
|
||||||
|
shortcutIndexByWorkspaceKey,
|
||||||
|
toggleProjectCollapsed,
|
||||||
|
setProjectCollapsed,
|
||||||
|
handleRefresh,
|
||||||
|
handleHostSelect,
|
||||||
|
handleOpenProject,
|
||||||
|
handleSettings,
|
||||||
|
isOpen,
|
||||||
|
handleViewMore,
|
||||||
|
}: DesktopSidebarProps) {
|
||||||
|
const dragHandlers = useTauriDragHandlers()
|
||||||
|
const trafficLightPadding = useTrafficLightPadding()
|
||||||
|
const hostStatusDotStyle = useMemo(
|
||||||
|
() => [styles.hostStatusDot, { backgroundColor: activeHostStatusColor }],
|
||||||
|
[activeHostStatusColor]
|
||||||
|
)
|
||||||
|
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -501,7 +642,6 @@ export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selecte
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Middle: scrollable project/workspace tree */}
|
|
||||||
{isInitialLoad ? (
|
{isInitialLoad ? (
|
||||||
<SidebarAgentListSkeleton />
|
<SidebarAgentListSkeleton />
|
||||||
) : (
|
) : (
|
||||||
@@ -517,7 +657,6 @@ export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selecte
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Footer */}
|
|
||||||
<View style={styles.sidebarFooter}>
|
<View style={styles.sidebarFooter}>
|
||||||
<View style={styles.footerHostSlot}>
|
<View style={styles.footerHostSlot}>
|
||||||
<Pressable
|
<Pressable
|
||||||
@@ -546,7 +685,7 @@ export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selecte
|
|||||||
accessible
|
accessible
|
||||||
accessibilityLabel="Add project"
|
accessibilityLabel="Add project"
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
onPress={handleOpenProjectDesktop}
|
onPress={handleOpenProject}
|
||||||
>
|
>
|
||||||
{({ hovered }) => (
|
{({ hovered }) => (
|
||||||
<Plus
|
<Plus
|
||||||
@@ -571,7 +710,7 @@ export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selecte
|
|||||||
accessible
|
accessible
|
||||||
accessibilityLabel="Settings"
|
accessibilityLabel="Settings"
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
onPress={handleSettingsDesktop}
|
onPress={handleSettings}
|
||||||
>
|
>
|
||||||
{({ hovered }) => (
|
{({ hovered }) => (
|
||||||
<Settings
|
<Settings
|
||||||
@@ -595,7 +734,7 @@ export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selecte
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create((theme) => ({
|
const styles = StyleSheet.create((theme) => ({
|
||||||
backdrop: {
|
backdrop: {
|
||||||
|
|||||||
Reference in New Issue
Block a user