diff --git a/src/renderer/src/components/MessageInput.tsx b/src/renderer/src/components/MessageInput.tsx index ce7ba44d78..0183c79e99 100644 --- a/src/renderer/src/components/MessageInput.tsx +++ b/src/renderer/src/components/MessageInput.tsx @@ -76,7 +76,7 @@ export function MessageInput({ {/* Folder selection prompt */}
- Select a folder to start... + Select a folder to start...
) } diff --git a/src/renderer/src/components/ui/sidebar.tsx b/src/renderer/src/components/ui/sidebar.tsx index 30638ac35f..b3f8f034fb 100644 --- a/src/renderer/src/components/ui/sidebar.tsx +++ b/src/renderer/src/components/ui/sidebar.tsx @@ -6,8 +6,14 @@ import { cva, type VariantProps } from "class-variance-authority" import { PanelLeftIcon } from "lucide-react" import { useIsMobile } from "@/hooks/use-mobile" +import { useResize } from "@/hooks/useResize" import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" +import { + useUIStore, + SIDEBAR_MIN_WIDTH, + SIDEBAR_MAX_WIDTH, +} from "@/stores/uiStore" import { Input } from "@/components/ui/input" import { Separator } from "@/components/ui/separator" import { @@ -27,7 +33,6 @@ import { const SIDEBAR_COOKIE_NAME = "sidebar_state" const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 -const SIDEBAR_WIDTH = "16rem" const SIDEBAR_WIDTH_MOBILE = "18rem" const SIDEBAR_WIDTH_ICON = "3rem" const SIDEBAR_KEYBOARD_SHORTCUT = "b" @@ -126,6 +131,9 @@ function SidebarProvider({ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] ) + // Get sidebar width from store + const sidebarWidth = useUIStore((s) => s.sidebarWidth) + return ( @@ -133,7 +141,7 @@ function SidebarProvider({ data-slot="sidebar-wrapper" style={ { - "--sidebar-width": SIDEBAR_WIDTH, + "--sidebar-width": `${sidebarWidth}px`, "--sidebar-width-icon": SIDEBAR_WIDTH_ICON, ...style, } as React.CSSProperties @@ -165,6 +173,17 @@ function Sidebar({ }) { const { isMobile, state, openMobile, setOpenMobile } = useSidebar() + // Resize functionality + const sidebarWidth = useUIStore((s) => s.sidebarWidth) + const setSidebarWidth = useUIStore((s) => s.setSidebarWidth) + const { isResizing, handleProps } = useResize({ + width: sidebarWidth, + minWidth: SIDEBAR_MIN_WIDTH, + maxWidth: SIDEBAR_MAX_WIDTH, + onWidthChange: setSidebarWidth, + direction: side === "left" ? "right" : "left", + }) + if (collapsible === "none") { return ( + {/* Resize handle - only show when expanded */} + {state === "expanded" && ( +
+ )}
) diff --git a/src/renderer/src/hooks/useResize.ts b/src/renderer/src/hooks/useResize.ts new file mode 100644 index 0000000000..4fd065341f --- /dev/null +++ b/src/renderer/src/hooks/useResize.ts @@ -0,0 +1,91 @@ +/** + * Hook for drag-to-resize functionality + */ +import { useCallback, useEffect, useRef, useState } from 'react' + +interface UseResizeOptions { + /** Current width */ + width: number + /** Minimum width */ + minWidth: number + /** Maximum width */ + maxWidth: number + /** Callback when width changes */ + onWidthChange: (width: number) => void + /** Direction of resize: 'left' means drag from left edge, 'right' means drag from right edge */ + direction: 'left' | 'right' +} + +interface UseResizeReturn { + /** Whether currently resizing */ + isResizing: boolean + /** Props to spread on the resize handle element */ + handleProps: { + onMouseDown: (e: React.MouseEvent) => void + style: React.CSSProperties + } +} + +export function useResize({ + width, + minWidth, + maxWidth, + onWidthChange, + direction, +}: UseResizeOptions): UseResizeReturn { + const [isResizing, setIsResizing] = useState(false) + const startXRef = useRef(0) + const startWidthRef = useRef(0) + + const handleMouseDown = useCallback( + (e: React.MouseEvent) => { + e.preventDefault() + setIsResizing(true) + startXRef.current = e.clientX + startWidthRef.current = width + }, + [width] + ) + + useEffect(() => { + if (!isResizing) return + + const handleMouseMove = (e: MouseEvent) => { + const delta = e.clientX - startXRef.current + // For left sidebar, dragging right increases width + // For right panel, dragging left increases width + const newWidth = + direction === 'right' + ? startWidthRef.current + delta + : startWidthRef.current - delta + const clampedWidth = Math.max(minWidth, Math.min(maxWidth, newWidth)) + onWidthChange(clampedWidth) + } + + const handleMouseUp = () => { + setIsResizing(false) + } + + document.addEventListener('mousemove', handleMouseMove) + document.addEventListener('mouseup', handleMouseUp) + + // Add cursor style to body during resize + document.body.style.cursor = 'col-resize' + document.body.style.userSelect = 'none' + + return () => { + document.removeEventListener('mousemove', handleMouseMove) + document.removeEventListener('mouseup', handleMouseUp) + document.body.style.cursor = '' + document.body.style.userSelect = '' + } + }, [isResizing, minWidth, maxWidth, onWidthChange, direction]) + + return { + isResizing, + handleProps: { + onMouseDown: handleMouseDown, + style: { cursor: 'col-resize' }, + }, + } +} diff --git a/src/renderer/src/stores/uiStore.ts b/src/renderer/src/stores/uiStore.ts index 4891dbfe52..2d6fc49878 100644 --- a/src/renderer/src/stores/uiStore.ts +++ b/src/renderer/src/stores/uiStore.ts @@ -5,16 +5,29 @@ import { create } from 'zustand' import { persist } from 'zustand/middleware' +// Width constraints +export const SIDEBAR_MIN_WIDTH = 200 +export const SIDEBAR_MAX_WIDTH = 400 +export const SIDEBAR_DEFAULT_WIDTH = 256 + +export const RIGHT_PANEL_MIN_WIDTH = 240 +export const RIGHT_PANEL_MAX_WIDTH = 480 +export const RIGHT_PANEL_DEFAULT_WIDTH = 320 + interface UIStore { // Sidebar state sidebarOpen: boolean + sidebarWidth: number toggleSidebar: () => void setSidebarOpen: (open: boolean) => void + setSidebarWidth: (width: number) => void // Right panel state rightPanelOpen: boolean + rightPanelWidth: number toggleRightPanel: () => void setRightPanelOpen: (open: boolean) => void + setRightPanelWidth: (width: number) => void } export const useUIStore = create()( @@ -22,13 +35,19 @@ export const useUIStore = create()( (set) => ({ // Sidebar - default open sidebarOpen: true, + sidebarWidth: SIDEBAR_DEFAULT_WIDTH, toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })), setSidebarOpen: (open) => set({ sidebarOpen: open }), + setSidebarWidth: (width) => + set({ sidebarWidth: Math.max(SIDEBAR_MIN_WIDTH, Math.min(SIDEBAR_MAX_WIDTH, width)) }), // Right panel - default open rightPanelOpen: true, + rightPanelWidth: RIGHT_PANEL_DEFAULT_WIDTH, toggleRightPanel: () => set((state) => ({ rightPanelOpen: !state.rightPanelOpen })), setRightPanelOpen: (open) => set({ rightPanelOpen: open }), + setRightPanelWidth: (width) => + set({ rightPanelWidth: Math.max(RIGHT_PANEL_MIN_WIDTH, Math.min(RIGHT_PANEL_MAX_WIDTH, width)) }), }), { name: 'ui-state', // localStorage key