From 387f1247c1c7bbea967fe88d99ca3748b7e04b8e Mon Sep 17 00:00:00 2001 From: Abhijay Jain Date: Fri, 20 Mar 2026 19:25:33 +0530 Subject: [PATCH] feat: ability to expand sidebar to see chats names (#7816) Signed-off-by: Abhijay007 Signed-off-by: Abhijay Jain --- ui/desktop/src/App.test.tsx | 2 + .../src/components/Layout/AppLayout.tsx | 114 ++++++++++++++++-- ui/desktop/src/components/Layout/constants.ts | 4 + ui/desktop/src/main.ts | 1 + ui/desktop/src/utils/settings.ts | 2 + 5 files changed, 114 insertions(+), 9 deletions(-) diff --git a/ui/desktop/src/App.test.tsx b/ui/desktop/src/App.test.tsx index 2907ae405c..09a9408365 100644 --- a/ui/desktop/src/App.test.tsx +++ b/ui/desktop/src/App.test.tsx @@ -171,6 +171,8 @@ const mockElectron = { getAllowedExtensions: vi.fn().mockResolvedValue([]), platform: 'darwin', createChatWindow: vi.fn(), + getSetting: vi.fn().mockResolvedValue(null), + setSetting: vi.fn().mockResolvedValue(undefined), }; // Mock appConfig diff --git a/ui/desktop/src/components/Layout/AppLayout.tsx b/ui/desktop/src/components/Layout/AppLayout.tsx index 7236e04ccf..c24a392671 100644 --- a/ui/desktop/src/components/Layout/AppLayout.tsx +++ b/ui/desktop/src/components/Layout/AppLayout.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import { Outlet, useLocation } from 'react-router-dom'; import { motion } from 'framer-motion'; import { Menu } from 'lucide-react'; @@ -34,6 +34,82 @@ const AppLayoutContent: React.FC = ({ activeSessions }) = isCondensedIconOnly, } = useNavigationContext(); + const [navWidth, setNavWidth] = useState(null); + const navWidthRef = useRef(null); + + useEffect(() => { + window.electron.getSetting('navExpandedWidth').then((delta) => { + if (delta !== null) { + setNavWidth( + Math.min( + NAV_DIMENSIONS.MAX_NAV_WIDTH, + Math.max(NAV_DIMENSIONS.MIN_NAV_WIDTH, NAV_DIMENSIONS.CONDENSED_WIDTH + delta) + ) + ); + } + }); + }, []); + + const isResizable = + !isHorizontalNav && !isCondensedIconOnly && effectiveNavigationMode === 'push' && isNavExpanded; + + const dragStateRef = useRef<{ startX: number; startWidth: number; direction: 1 | -1 } | null>( + null + ); + const navRef = useRef(null); + + const onMouseMove = useCallback((e: MouseEvent) => { + if (!dragStateRef.current) return; + const delta = (e.clientX - dragStateRef.current.startX) * dragStateRef.current.direction; + const newWidth = Math.min( + NAV_DIMENSIONS.MAX_NAV_WIDTH, + Math.max(NAV_DIMENSIONS.MIN_NAV_WIDTH, dragStateRef.current.startWidth + delta) + ); + navWidthRef.current = newWidth; + setNavWidth(newWidth); + }, []); + + const onMouseUp = useCallback(() => { + dragStateRef.current = null; + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + window.removeEventListener('mousemove', onMouseMove); + window.removeEventListener('mouseup', onMouseUp); + if (navWidthRef.current !== null) { + window.electron.setSetting( + 'navExpandedWidth', + navWidthRef.current - NAV_DIMENSIONS.CONDENSED_WIDTH + ); + } + }, [onMouseMove]); + + const onHandleMouseDown = useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + const currentWidth = + navRef.current?.getBoundingClientRect().width ?? NAV_DIMENSIONS.CONDENSED_WIDTH; + dragStateRef.current = { + startX: e.clientX, + startWidth: currentWidth, + direction: navigationPosition === 'right' ? -1 : 1, + }; + document.body.style.cursor = 'col-resize'; + document.body.style.userSelect = 'none'; + window.addEventListener('mousemove', onMouseMove); + window.addEventListener('mouseup', onMouseUp); + }, + [navigationPosition, onMouseMove, onMouseUp] + ); + + useEffect(() => { + return () => { + window.removeEventListener('mousemove', onMouseMove); + window.removeEventListener('mouseup', onMouseUp); + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + }; + }, [onMouseMove, onMouseUp]); + if (!chatContext) { throw new Error('AppLayoutContent must be used within ChatProvider'); } @@ -134,6 +210,7 @@ const AppLayoutContent: React.FC = ({ activeSessions }) = {/* Push mode navigation (inline) with animation */} {effectiveNavigationMode === 'push' && ( = ({ activeSessions }) = ? '100%' : isNavExpanded ? effectiveNavigationStyle === 'expanded' - ? '30%' + ? (navWidth ?? '30%') : isCondensedIconOnly ? NAV_DIMENSIONS.CONDENSED_ICON_ONLY_WIDTH - : NAV_DIMENSIONS.CONDENSED_WIDTH + : (navWidth ?? NAV_DIMENSIONS.CONDENSED_WIDTH) : 0, height: isHorizontalNav ? isNavExpanded @@ -161,7 +238,9 @@ const AppLayoutContent: React.FC = ({ activeSessions }) = }} style={{ maxWidth: - !isHorizontalNav && effectiveNavigationStyle === 'expanded' ? '400px' : undefined, + !isHorizontalNav && effectiveNavigationStyle === 'expanded' + ? NAV_DIMENSIONS.MAX_NAV_WIDTH + : undefined, minWidth: !isHorizontalNav && effectiveNavigationStyle === 'condensed' && isNavExpanded ? isCondensedIconOnly @@ -177,14 +256,31 @@ const AppLayoutContent: React.FC = ({ activeSessions }) = height: !isHorizontalNav ? '100%' : undefined, }} className={cn( - 'flex-shrink-0', - effectiveNavigationStyle === 'condensed' && !isHorizontalNav - ? 'overflow-visible' - : 'overflow-hidden', + 'relative flex-shrink-0 overflow-visible', isHorizontalNav ? 'w-full' : 'h-full' )} > - +
+ +
+ {isResizable && ( +
+
+
+ )} )} diff --git a/ui/desktop/src/components/Layout/constants.ts b/ui/desktop/src/components/Layout/constants.ts index 5a73b631c5..81d3acdf9d 100644 --- a/ui/desktop/src/components/Layout/constants.ts +++ b/ui/desktop/src/components/Layout/constants.ts @@ -7,6 +7,10 @@ export const NAV_DIMENSIONS = { EXPANDED_HEIGHT: 180, /** Height of condensed navigation (horizontal mode) */ CONDENSED_HEIGHT: 46, + /** Minimum width when resizing the navigation panel */ + MIN_NAV_WIDTH: 200, + /** Maximum width when resizing the navigation panel */ + MAX_NAV_WIDTH: 600, } as const; export const Z_INDEX = { diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index 359b3ba52a..0cd2a8e18e 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -1323,6 +1323,7 @@ const validSettingKeys: Set = new Set([ 'showPricing', 'sessionSharing', 'seenAnnouncementIds', + 'navExpandedWidth', ]); ipcMain.handle('set-setting', (_event, key: SettingKey, value: unknown) => { diff --git a/ui/desktop/src/utils/settings.ts b/ui/desktop/src/utils/settings.ts index 31226f0685..f23cdd5924 100644 --- a/ui/desktop/src/utils/settings.ts +++ b/ui/desktop/src/utils/settings.ts @@ -44,6 +44,7 @@ export interface Settings { showPricing: boolean; sessionSharing: SessionSharingConfig; seenAnnouncementIds: string[]; + navExpandedWidth: number | null; } export type SettingKey = keyof Settings; @@ -85,6 +86,7 @@ export const defaultSettings: Settings = { baseUrl: '', }, seenAnnouncementIds: [], + navExpandedWidth: null, }; export function getKeyboardShortcuts(settings: Settings): KeyboardShortcuts {