feat: ability to expand sidebar to see chats names (#7816)
Signed-off-by: Abhijay007 <Abhijay007j@gmail.com> Signed-off-by: Abhijay Jain <Abhijay007j@gmail.com>
This commit is contained in:
@@ -171,6 +171,8 @@ const mockElectron = {
|
|||||||
getAllowedExtensions: vi.fn().mockResolvedValue([]),
|
getAllowedExtensions: vi.fn().mockResolvedValue([]),
|
||||||
platform: 'darwin',
|
platform: 'darwin',
|
||||||
createChatWindow: vi.fn(),
|
createChatWindow: vi.fn(),
|
||||||
|
getSetting: vi.fn().mockResolvedValue(null),
|
||||||
|
setSetting: vi.fn().mockResolvedValue(undefined),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mock appConfig
|
// Mock appConfig
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { Outlet, useLocation } from 'react-router-dom';
|
import { Outlet, useLocation } from 'react-router-dom';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Menu } from 'lucide-react';
|
import { Menu } from 'lucide-react';
|
||||||
@@ -34,6 +34,82 @@ const AppLayoutContent: React.FC<AppLayoutContentProps> = ({ activeSessions }) =
|
|||||||
isCondensedIconOnly,
|
isCondensedIconOnly,
|
||||||
} = useNavigationContext();
|
} = useNavigationContext();
|
||||||
|
|
||||||
|
const [navWidth, setNavWidth] = useState<number | null>(null);
|
||||||
|
const navWidthRef = useRef<number | null>(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<HTMLDivElement>(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) {
|
if (!chatContext) {
|
||||||
throw new Error('AppLayoutContent must be used within ChatProvider');
|
throw new Error('AppLayoutContent must be used within ChatProvider');
|
||||||
}
|
}
|
||||||
@@ -134,6 +210,7 @@ const AppLayoutContent: React.FC<AppLayoutContentProps> = ({ activeSessions }) =
|
|||||||
{/* Push mode navigation (inline) with animation */}
|
{/* Push mode navigation (inline) with animation */}
|
||||||
{effectiveNavigationMode === 'push' && (
|
{effectiveNavigationMode === 'push' && (
|
||||||
<motion.div
|
<motion.div
|
||||||
|
ref={navRef}
|
||||||
key="push-nav"
|
key="push-nav"
|
||||||
initial={false}
|
initial={false}
|
||||||
animate={{
|
animate={{
|
||||||
@@ -141,10 +218,10 @@ const AppLayoutContent: React.FC<AppLayoutContentProps> = ({ activeSessions }) =
|
|||||||
? '100%'
|
? '100%'
|
||||||
: isNavExpanded
|
: isNavExpanded
|
||||||
? effectiveNavigationStyle === 'expanded'
|
? effectiveNavigationStyle === 'expanded'
|
||||||
? '30%'
|
? (navWidth ?? '30%')
|
||||||
: isCondensedIconOnly
|
: isCondensedIconOnly
|
||||||
? NAV_DIMENSIONS.CONDENSED_ICON_ONLY_WIDTH
|
? NAV_DIMENSIONS.CONDENSED_ICON_ONLY_WIDTH
|
||||||
: NAV_DIMENSIONS.CONDENSED_WIDTH
|
: (navWidth ?? NAV_DIMENSIONS.CONDENSED_WIDTH)
|
||||||
: 0,
|
: 0,
|
||||||
height: isHorizontalNav
|
height: isHorizontalNav
|
||||||
? isNavExpanded
|
? isNavExpanded
|
||||||
@@ -161,7 +238,9 @@ const AppLayoutContent: React.FC<AppLayoutContentProps> = ({ activeSessions }) =
|
|||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
maxWidth:
|
maxWidth:
|
||||||
!isHorizontalNav && effectiveNavigationStyle === 'expanded' ? '400px' : undefined,
|
!isHorizontalNav && effectiveNavigationStyle === 'expanded'
|
||||||
|
? NAV_DIMENSIONS.MAX_NAV_WIDTH
|
||||||
|
: undefined,
|
||||||
minWidth:
|
minWidth:
|
||||||
!isHorizontalNav && effectiveNavigationStyle === 'condensed' && isNavExpanded
|
!isHorizontalNav && effectiveNavigationStyle === 'condensed' && isNavExpanded
|
||||||
? isCondensedIconOnly
|
? isCondensedIconOnly
|
||||||
@@ -177,14 +256,31 @@ const AppLayoutContent: React.FC<AppLayoutContentProps> = ({ activeSessions }) =
|
|||||||
height: !isHorizontalNav ? '100%' : undefined,
|
height: !isHorizontalNav ? '100%' : undefined,
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex-shrink-0',
|
'relative flex-shrink-0 overflow-visible',
|
||||||
effectiveNavigationStyle === 'condensed' && !isHorizontalNav
|
|
||||||
? 'overflow-visible'
|
|
||||||
: 'overflow-hidden',
|
|
||||||
isHorizontalNav ? 'w-full' : 'h-full'
|
isHorizontalNav ? 'w-full' : 'h-full'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Navigation />
|
<div
|
||||||
|
className={cn(
|
||||||
|
'w-full h-full',
|
||||||
|
effectiveNavigationStyle === 'condensed' && !isHorizontalNav
|
||||||
|
? 'overflow-visible'
|
||||||
|
: 'overflow-hidden'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Navigation />
|
||||||
|
</div>
|
||||||
|
{isResizable && (
|
||||||
|
<div
|
||||||
|
onMouseDown={onHandleMouseDown}
|
||||||
|
className={cn(
|
||||||
|
'absolute top-0 w-2 h-full z-20 cursor-col-resize group flex items-center justify-center',
|
||||||
|
navigationPosition === 'right' ? '-left-1' : '-right-1'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="w-px h-full bg-border-subtle opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ export const NAV_DIMENSIONS = {
|
|||||||
EXPANDED_HEIGHT: 180,
|
EXPANDED_HEIGHT: 180,
|
||||||
/** Height of condensed navigation (horizontal mode) */
|
/** Height of condensed navigation (horizontal mode) */
|
||||||
CONDENSED_HEIGHT: 46,
|
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;
|
} as const;
|
||||||
|
|
||||||
export const Z_INDEX = {
|
export const Z_INDEX = {
|
||||||
|
|||||||
@@ -1323,6 +1323,7 @@ const validSettingKeys: Set<string> = new Set([
|
|||||||
'showPricing',
|
'showPricing',
|
||||||
'sessionSharing',
|
'sessionSharing',
|
||||||
'seenAnnouncementIds',
|
'seenAnnouncementIds',
|
||||||
|
'navExpandedWidth',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
ipcMain.handle('set-setting', (_event, key: SettingKey, value: unknown) => {
|
ipcMain.handle('set-setting', (_event, key: SettingKey, value: unknown) => {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export interface Settings {
|
|||||||
showPricing: boolean;
|
showPricing: boolean;
|
||||||
sessionSharing: SessionSharingConfig;
|
sessionSharing: SessionSharingConfig;
|
||||||
seenAnnouncementIds: string[];
|
seenAnnouncementIds: string[];
|
||||||
|
navExpandedWidth: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SettingKey = keyof Settings;
|
export type SettingKey = keyof Settings;
|
||||||
@@ -85,6 +86,7 @@ export const defaultSettings: Settings = {
|
|||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
},
|
},
|
||||||
seenAnnouncementIds: [],
|
seenAnnouncementIds: [],
|
||||||
|
navExpandedWidth: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getKeyboardShortcuts(settings: Settings): KeyboardShortcuts {
|
export function getKeyboardShortcuts(settings: Settings): KeyboardShortcuts {
|
||||||
|
|||||||
Reference in New Issue
Block a user