mirror of
https://github.com/block/goose.git
synced 2026-07-17 12:56:20 +02:00
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([]),
|
||||
platform: 'darwin',
|
||||
createChatWindow: vi.fn(),
|
||||
getSetting: vi.fn().mockResolvedValue(null),
|
||||
setSetting: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
|
||||
// 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 { motion } from 'framer-motion';
|
||||
import { Menu } from 'lucide-react';
|
||||
@@ -34,6 +34,82 @@ const AppLayoutContent: React.FC<AppLayoutContentProps> = ({ activeSessions }) =
|
||||
isCondensedIconOnly,
|
||||
} = 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) {
|
||||
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 */}
|
||||
{effectiveNavigationMode === 'push' && (
|
||||
<motion.div
|
||||
ref={navRef}
|
||||
key="push-nav"
|
||||
initial={false}
|
||||
animate={{
|
||||
@@ -141,10 +218,10 @@ const AppLayoutContent: React.FC<AppLayoutContentProps> = ({ 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<AppLayoutContentProps> = ({ 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<AppLayoutContentProps> = ({ 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'
|
||||
)}
|
||||
>
|
||||
<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>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -1323,6 +1323,7 @@ const validSettingKeys: Set<string> = new Set([
|
||||
'showPricing',
|
||||
'sessionSharing',
|
||||
'seenAnnouncementIds',
|
||||
'navExpandedWidth',
|
||||
]);
|
||||
|
||||
ipcMain.handle('set-setting', (_event, key: SettingKey, value: unknown) => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user