diff --git a/desktop/src/components/layout/AppShell.tsx b/desktop/src/components/layout/AppShell.tsx index 37e3e10e..44245885 100644 --- a/desktop/src/components/layout/AppShell.tsx +++ b/desktop/src/components/layout/AppShell.tsx @@ -14,6 +14,7 @@ import { useTranslation } from '../../i18n' export function AppShell() { const fetchSettings = useSettingsStore((s) => s.fetchAll) + const sidebarOpen = useUIStore((s) => s.sidebarOpen) const [ready, setReady] = useState(false) const [startupError, setStartupError] = useState(null) const t = useTranslation() @@ -94,9 +95,19 @@ export function AppShell() { } return ( -
- -
+
+
+ +
+
diff --git a/desktop/src/components/layout/Sidebar.test.tsx b/desktop/src/components/layout/Sidebar.test.tsx index a44f04ae..48bd620a 100644 --- a/desktop/src/components/layout/Sidebar.test.tsx +++ b/desktop/src/components/layout/Sidebar.test.tsx @@ -25,6 +25,8 @@ vi.mock('../../i18n', () => ({ 'sidebar.timeGroup.last30days': 'Last 30 Days', 'sidebar.timeGroup.older': 'Older', 'sidebar.missingDir': 'Missing', + 'sidebar.collapse': 'Collapse sidebar', + 'sidebar.expand': 'Expand sidebar', } return translations[key] ?? key @@ -70,6 +72,7 @@ describe('Sidebar', () => { disconnectSession, } as Partial>) useUIStore.setState({ + sidebarOpen: true, addToast, } as Partial>) }) @@ -155,4 +158,25 @@ describe('Sidebar', () => { expect(useTabStore.getState().tabs).toEqual([]) expect(useTabStore.getState().activeTabId).toBeNull() }) + + it('collapses into an icon rail and expands back', async () => { + render() + + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: 'Collapse sidebar' })) + }) + + expect(useUIStore.getState().sidebarOpen).toBe(false) + expect(screen.queryByPlaceholderText('Search sessions')).not.toBeInTheDocument() + expect(screen.getByRole('complementary')).toHaveAttribute('data-state', 'closed') + expect(screen.getByTestId('sidebar-expand-button')).toHaveClass('sidebar-toggle-button--collapsed') + + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: 'Expand sidebar' })) + }) + + expect(useUIStore.getState().sidebarOpen).toBe(true) + expect(screen.getByPlaceholderText('Search sessions')).toBeInTheDocument() + expect(screen.getByRole('complementary')).toHaveAttribute('data-state', 'open') + }) }) diff --git a/desktop/src/components/layout/Sidebar.tsx b/desktop/src/components/layout/Sidebar.tsx index 1eeef738..3599f2b2 100644 --- a/desktop/src/components/layout/Sidebar.tsx +++ b/desktop/src/components/layout/Sidebar.tsx @@ -22,6 +22,8 @@ export function Sidebar() { const deleteSession = useSessionStore((s) => s.deleteSession) const renameSession = useSessionStore((s) => s.renameSession) const addToast = useUIStore((s) => s.addToast) + const sidebarOpen = useUIStore((s) => s.sidebarOpen) + const toggleSidebar = useUIStore((s) => s.toggleSidebar) const activeTabId = useTabStore((s) => s.activeTabId) const closeTab = useTabStore((s) => s.closeTab) const disconnectSession = useChatStore((s) => s.disconnectSession) @@ -34,7 +36,11 @@ export function Sidebar() { fetchSessions() }, [fetchSessions]) - // Close context menu on click outside + useEffect(() => { + if (!contextMenu || sidebarOpen) return + setContextMenu(null) + }, [contextMenu, sidebarOpen]) + useEffect(() => { if (!contextMenu) return const close = () => setContextMenu(null) @@ -42,7 +48,6 @@ export function Sidebar() { return () => document.removeEventListener('click', close) }, [contextMenu]) - // Filter by selected projects, then by search query const filteredSessions = useMemo(() => { let result = sessions if (selectedProjects.length > 0) { @@ -55,7 +60,6 @@ export function Sidebar() { return result }, [sessions, selectedProjects, searchQuery]) - // Group by time const timeGroups = useMemo(() => groupByTime(filteredSessions), [filteredSessions]) const handleContextMenu = useCallback((e: React.MouseEvent, id: string) => { @@ -103,7 +107,7 @@ export function Sidebar() { const t = useTranslation() - const TIME_GROUP_LABELS: Record = { + const timeGroupLabels: Record = { today: t('sidebar.timeGroup.today'), yesterday: t('sidebar.timeGroup.yesterday'), last7days: t('sidebar.timeGroup.last7days'), @@ -112,32 +116,56 @@ export function Sidebar() { } return ( -